import { useUserStore } from '@/store/modules/user'
import type {
  AxiosError,
  AxiosInstance,
  AxiosRequestConfig,
  InternalAxiosRequestConfig,
  AxiosResponse,
} from 'axios'
import axios from 'axios'
import { createUniAppAxiosAdapter } from '@uni-helper/axios-adapter'
import { loginOverdueToPage, showToast } from '@/utils'
import { SETTING } from '@/config'

export type CustomMethod = 'get' | 'post' | 'put' | 'delete'

// 请求响应参数
export interface ResultData<T = any> {
  code: number
  success: boolean
  message: string
  data?: T
  timestamp: number
}

//文件上传参数
export interface UploadFileParams {
  // 文件参数接口字段名
  name?: string
  // 文件
  file: File | Blob
  // 文件名
  filename?: string
  [key: string]: any
}

// 请求结果集
export enum ResultEnum {
  SUCCESS = 200, // 请求成功
  FAIL = 500, // 请求失败
  OVERDUE = 401, // 登录失效
  NOT_FOUND = 404,
  TIMEOUT = 60000,
}

// 常用的contentTyp类型
export enum ContentTypeEnum {
  // json
  JSON = 'application/json;charset=UTF-8',
  // text
  TEXT = 'text/plain;charset=UTF-8',
  // xml
  XML = 'application/xml;charset=UTF-8',
  // application/x-www-form-urlencoded 一般配合qs
  FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
  // form-data  上传
  FORM_DATA = 'multipart/form-data;charset=UTF-8',
}

// 白名单 不判断是否登录过期等异常问题
const whitelist: string[] = ['/user/login', '/user/register']

const URL: string = import.meta.env.VITE_GLOB_DOMAIN_URL
console.log('当前环境地址:', URL)

const config = {
  // 默认地址
  baseURL: URL,
  // 设置超时时间
  timeout: ResultEnum.TIMEOUT as number,
  // 跨域时候允许携带凭证
  withCredentials: true,
  headers: {
    'Content-Type': ContentTypeEnum.JSON,
  },
  adapter: createUniAppAxiosAdapter(), // 指定适配器
}

class RequestHttp {
  // 定义成员变量并指定类型
  service: AxiosInstance

  public constructor(config: AxiosRequestConfig) {
    // 实例化axios
    this.service = axios.create(config)

    /**
     * 请求拦截器
     * 客户端发送请求 -> [请求拦截器] -> 服务器
     * token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
     */
    this.service.interceptors.request.use(
      (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
        // console.log('请求拦截器:', config)
        if (SETTING.HAS_LOGIN) {
          const userStore = useUserStore()
          const token = userStore.token

          // 如果请求的接口不在白名单中，并且没有token，则跳转到登录页面
          if (!token && !whitelist.includes(config.url || '')) {
            throw new Error('请先登录') // 抛出错误
          }

          if (token) {
            config.headers['Authorization'] = token
          }
        }

        return config
      },
      (error: AxiosError) => {
        // 请求报错
        console.log('请求拦截器报错:', error)
        Promise.reject(error)
      },
    )

    /**
     * 响应拦截器
     * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
     */
    this.service.interceptors.response.use(
      (response: AxiosResponse) => {
        // console.log('响应拦截器', response)
        const { config, data } = response // 解构

        if (SETTING.HAS_LOGIN && !whitelist.includes(config.url || '')) {
          if (data.code === ResultEnum.OVERDUE) {
            // 登录信息失效，应跳转到登录页面，并清空本地的token
            setTimeout(() => {
              showToast('登录已失效,请重新登录!')
            }, 300)
            const userStore = useUserStore()
            userStore.logout()
            loginOverdueToPage() // 用于跳转到登录页
            return Promise.reject(data)
          }

          // 全局错误信息拦截（防止下载文件得时候返回数据流，没有code，直接报错）
          if (data?.code !== ResultEnum.SUCCESS) {
            showToast(data.message || '系统异常，请联系管理员!')
            return Promise.reject(data)
          }
        }

        return data
      },
      (error: AxiosError) => {
        console.log('响应拦截器报错:', error)

        const { response } = error
        if (response) {
          this.handleCode(response.status)
        }
        // 不加这句话会进.then
        return Promise.reject()
      },
    )
  }

  handleCode(code: number): void {
    const userStore = useUserStore()

    let errMessage = ''
    switch (code) {
      case 401:
        setTimeout(() => {
          uni.showToast({ title: '登录已过期,请重新登录!', icon: 'none' })
        }, 300)
        // 如果当前页面不是登录页面则跳转至登录页面
        userStore.logout()
        loginOverdueToPage()
        break
      case 403:
        errMessage = '用户得到授权，但是访问是被禁止的!'
        break
      case 404:
        errMessage = '网络请求错误,未找到该资源!'
        break
      case 405:
        errMessage = '网络请求错误,请求方法未允许!'
        break
      case 408:
        errMessage = '网络请求超时!'
        break
      case 500:
        errMessage = '服务器错误,请联系管理员!'
        break
      case 501:
        errMessage = '网络未实现!'
        break
      case 502:
        errMessage = '网络错误!'
        break
      case 503:
        errMessage = '服务不可用，服务器暂时过载或维护!'
        break
      case 504:
        errMessage = '网络超时!'
        break
      case 505:
        errMessage = 'http版本不支持该请求!'
        break
      default:
    }

    if (errMessage) {
      showToast(errMessage)
    }
  }

  // 常用方法封装
  http<T>(method: CustomMethod, url: string, params?: object, headers?: object): Promise<T> {
    let result: Promise<T>
    switch (method) {
      case 'get':
      case 'delete':
        result = this.service[method](url, { params, headers })
        break
      case 'post':
      case 'put':
        result = this.service.post(url, params, { headers })
        break
    }
    return result
  }

  // 常用方法封装
  get<T>(url: string, params?: object, headers?: object): Promise<ResultData<T>> {
    return this.service.get(url, { params, headers })
  }

  post<T>(url: string, params?: object, headers?: object): Promise<ResultData<T>> {
    return this.service.post(url, params, { headers })
  }

  put<T>(url: string, params?: object, headers?: object): Promise<ResultData<T>> {
    return this.service.put(url, params, { headers })
  }

  delete<T>(url: string, params?: object, headers?: object): Promise<ResultData<T>> {
    return this.service.delete(url, { params, headers })
  }

  uploadFile<T>(params: UploadFileParams, headers?: object): Promise<ResultData<T>> {
    const baseApiUrl = import.meta.env.VITE_GLOB_DOMAIN_URL
    const uploadPath = import.meta.env.VITE_GLOB_UPLOAD_PATH
    const formData = new window.FormData()
    const customFilename = params.name || 'file'

    if (params.filename) {
      formData.append(customFilename, params.file, params.filename)
    } else {
      formData.append(customFilename, params.file)
    }
    let url = baseApiUrl + uploadPath
    let tHeaders = Object.assign({}, headers, {
      'Content-type': ContentTypeEnum.FORM_DATA,
    })
    return this.service.post(url, formData, { headers: tHeaders })
  }
}

// 导出一个实例对象
export default new RequestHttp(config)
